
namespace Huirui.Cavan.Presentation.Seedwork
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Web.Mvc;
    using Huirui.Cavan.Core.Message;
    using Huirui.Cavan.Core.Extension;
    using Huirui.Cavan.Core.Persistent;

    /// <summary>
    ///     The factory based on the naming Conventions.
    ///     If more implementors are found, project.config settings are used to determine priority
    /// </summary>
    public class DefaultFactory : IFactory
    {
        #region members & properties
        readonly IEnumerable<Type> _typeFilter = new List<Type>
        {
            typeof(ICoreModel),
            typeof(ISearch)
        };

        IDictionary<Type, ConstructorInfo> _pairs;
        IEnumerable<Type> _types;

        /// <summary>
        ///     Readonly list of the loaded types which implement IDao, IFacade, ICoreModel, IController implementors
        ///     and contains parameterless constructor
        /// </summary>
        public virtual IEnumerable<Type> Types
        {
            get { return _types ?? (_types = LoadKnownTypes()); }
        }

        /// <summary>
        ///     The cache of the "implemented" types and their "implementor" constructors to be invoked.
        /// </summary>
        public virtual IDictionary<Type, ConstructorInfo> Implementation
        {
            get { return _pairs ?? (_pairs = CreateTypeDictionary()); }
        }

        /// <summary>
        ///     Returns the collection of base interface types to be searched and used by this factory.
        ///     if not overrided, IDao, IFacade, ICoreModel and IController sub types are used
        /// </summary>
        public virtual IEnumerable<Type> TypeFilter
        {
            get { return _typeFilter; }
        }
        #endregion  members & properties

        #region IFactory
        /// <summary>
        ///     Creates the instance of type 'T'
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <returns></returns>
        public virtual T CreateInstance<T>(IList<AppMessage> messages = null)
        {
            return (T)CreateInstance(typeof(T), messages);
        }

        /// <summary>
        ///     Creates the instance of the 'Type'
        /// </summary>
        /// <param name = "type">the type to be created</param>
        /// <param name = "messages">Expected parameter is IList&lt;Message&gt; - to be set as Messages</param>
        /// <returns>null or instance of required type</returns>
        public virtual object CreateInstance(Type type, IList<AppMessage> messages = null)
        {
            if (!Implementation.ContainsKey(type))
            {
                return null;
            }

            var result = Implementation[type].Invoke(null);

            return SetMessages(messages, result);
        }
        #endregion

        #region Messages
        /// <summary>
        ///     If the return type is ICoreModel or IFacade
        ///     and args contains the messages --
        ///     Inject them in the Messages property
        /// </summary>
        /// <param name = "messages"></param>
        /// <param name = "result"></param>
        /// <returns></returns>
        protected virtual object SetMessages(IList<AppMessage> messages, object result)
        {
            messages = messages ?? new List<AppMessage>();

            var messageHolder = result as IMessagesHolder;

            if (messageHolder.Is())
            {
                messageHolder.Messages = messages;
            }
            return result;
        }
        #endregion Messages

        #region load known types
        /// <summary>
        ///     Loads all assemblies and searches for the IDao, IFacade, ICoreModel, IController implementors.
        ///     Only parameterless constructors are used
        /// </summary>
        /// <returns></returns>
        protected virtual IEnumerable<Type> LoadKnownTypes()
        {
            var types = new List<Type>();

            var path = AppDomain.CurrentDomain.RelativeSearchPath.IsNotEmpty()
                           ? AppDomain.CurrentDomain.RelativeSearchPath
                           : AppDomain.CurrentDomain.BaseDirectory;

            var dir = new DirectoryInfo(path);

            foreach (var file in dir.GetFiles("*.dll", SearchOption.TopDirectoryOnly))
            {
                try
                {
                    AppDomain.CurrentDomain.Load(file.Name.Replace(".dll", string.Empty));
                }
                catch (Exception ex)
                {
                    (typeof(DefaultFactory)).Log().Fatal("Concrete type Factory Cannot load an assembly", ex);
                }
            }

            foreach (var currentassembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    var allTypes = currentassembly.GetTypes();
                    var allInterfaces = allTypes
                        .Where(t => t.GetInterfaces()
                                        .Any(i => TypeFilter.Contains(i)));
                    var loaded = allInterfaces
                        .Where(t => t.GetConstructors()
                                        .Any(c => c.IsPublic
                                                  && c.GetParameters().Count().Equals(0)));
                    types.AddRange(loaded);
                }
                catch (Exception ex)
                {
                    (typeof(DefaultFactory)).Log().Fatal("Concrete type Factory Cannot load an assembly", ex);
                }
            }
            return types.AsReadOnly();
        }

        /// <summary>
        ///     Iterates the "Types" list and Creates the Dictionary "Implementation" with the
        ///     1) key as the known type and 2) value as a parameterless constructor of its implementor
        /// </summary>
        /// <returns></returns>
        protected virtual IDictionary<Type, ConstructorInfo> CreateTypeDictionary()
        {
            var implementation = new Dictionary<Type, ConstructorInfo>();

            foreach (var type in Types)
            {
                var interfaces = type.GetInterfaces()
                    .Where(t => t.GetInterfaces()
                                    .Any(i => TypeFilter.Contains(i)));
                foreach (var inter in interfaces)
                {
                    if (implementation.ContainsKey(inter))
                    {
                        if (!HasPriority(inter, type))
                        {
                            continue;
                        }
                    }
                    implementation[inter] = type.GetConstructor(new Type[0]);
                }
                implementation[type] = type.GetConstructor(new Type[0]);
            }
            return implementation;
        }

        /// <summary>
        ///     This method is called when there is a second "implementor" discovered while
        ///     Cache already contains another implmentor for asked "implemented" type.
        /// </summary>
        /// <param name = "implemented">the type to be later instantiated with the implmentor</param>
        /// <param name = "implementor">the latest implementor fo the "implmented" type.</param>
        /// <returns>True if this (latest) implementor is decided to have priority</returns>
        protected virtual bool HasPriority(Type implemented, Type implementor)
        {
            if (implemented.IsNull()
                || implementor.IsNull())
            {
                return false;
            }

            return false;
        }
        #endregion load known types
    }
}